package com.bright.hxj.qchz.service;

import cn.hutool.core.util.IdUtil;
import com.bright.ghj.common.cache.RedisCache;
import com.bright.ghj.common.util.DateUtil;
import com.bright.hxj.qchz.constant.SystemConstant;
import com.bright.hxj.qchz.pojo.bo.DuijieReturnMsg;
import com.bright.hxj.qchz.pojo.bo.NewRules;
import com.bright.hxj.qchz.pojo.bo.RoleInfo;
import com.bright.hxj.qchz.pojo.command.SubmitTaskCommand;
import com.bright.hxj.qchz.pojo.po.*;
import com.bright.hxj.qchz.util.CommonUtil;
import com.bright.hxj.qchz.util.NBExpressUtil;
import com.bright.hxj.qchz.util.QCHZExpressUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.json.JSONArray;
import org.json.JSONObject;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang.StringUtils;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Component;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.text.ParseException;
import java.util.*;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.TimeUnit;

@Component
@Slf4j
@RequiredArgsConstructor
public class Duijie_ShengJieShou_QCHZManager {

    private final Duijie_ShiJieShouDeployManager duijie_shiJieShouDeployManager;
    private final Duijie_QCHZ_GetRuleManager duijie_qchz_getRuleManager;

    private final DistManager distManager;
    private final TaskManager taskManager;
    private final SubmitTaskService submitTaskService;
    private final Duijie_QCHZ_DataManager dataManager;

//    private final ThreadPoolTaskExecutor executorService;
//    private final int maxThreadSize;



    // 对接数据所属年份 清查编号 清查名称 查清产核资qc表取得
//    private int years = 0;
//    private String qcNo = null, qcName = null;

    public boolean checkSignValid(JSONObject jsonObject, Duijie_ShiJieShouUser user) throws UnsupportedEncodingException, NoSuchAlgorithmException, InvalidKeyException {
        // 验签 使用
        // 根据实际接口结构调整取值
//        String appSecret = jsonObject.getString("appSecret");
        String method = jsonObject.getString("method");
        String nonce = jsonObject.getString("nonce");
        String timestamp = jsonObject.getString("timestamp");
        String sign = jsonObject.getString("sign");

        // 生成签名值

        String realSignValue = getSign(user, method, nonce, timestamp);
        return StringUtils.equals(sign, realSignValue);
    }

    private String getSign(Duijie_ShiJieShouUser user, String method, String nonce, String timestamp) throws NoSuchAlgorithmException, InvalidKeyException {
        // 设计签名字符串 p1=/duijieQCHZ.do?method=pushBaseInfo;\np2=Ty17pDvpVE6f5vLp;\np3=<申请得到的AccessKey>;\np4=2022-04-09 10:32:28
        String content = "p1=" + method +"\np2=" + nonce + "\np3=" + user.getPassword() + "\np4=" + timestamp;
        Mac hmacSha256 = Mac.getInstance("HmacSHA256");
        byte[] keyBytes = user.getPassword().getBytes(StandardCharsets.UTF_8);
        hmacSha256.init(new SecretKeySpec(keyBytes,0,keyBytes.length,"HmacSHA256"));
        return new String(Base64.encodeBase64(hmacSha256.doFinal(content.getBytes(StandardCharsets.UTF_8)),false));
    }

    public boolean checkTimestampValid(JSONObject jsonObject) throws ParseException {
        // 校验时间戳是否过期 两分钟内有效

        String timestamp = jsonObject.getString("timestamp");
        Date timestampDate = DateUtil.parseStringFormat(timestamp, "yyyy-MM-dd HH:mm:ss");
        Calendar timestampCal = Calendar.getInstance();
        timestampCal.setTime(timestampDate);
        Calendar earliestCal = Calendar.getInstance();
        earliestCal.add(Calendar.MINUTE, -2);
        return timestampCal.compareTo(earliestCal) > 0 && timestampCal.compareTo(Calendar.getInstance()) <= 0;
    }

    public DuijieReturnMsg insertOrUpdate(Duijie_ShiJieShouUser user, JSONObject jsonObject, String methodName) {
        // 结构转换 改成通用代码需要的结构
        JSONObject formattedJSONObj = new JSONObject();
        formattedJSONObj.put("userName", user.getUserName());
        formattedJSONObj.put("fileName", methodName);

        JSONArray data = jsonObject.getJSONArray("data");
        JSONArray formattedData = new JSONArray();

        // 赋值qcNo  qcName 查qc表
        // 改在DataManager里初始化时执行了
//        if (years == 0) {
//            dataManager.initYears();
//        }
//        int years = 2022;
//        String qcNo = "QC-2022", qcName = "2022三资清查";
        //  赋值地区号、账套号等
        String ztId, distNo, distName;
        String ztName = null, ztJc = null;
        if (jsonObject.has("ztid")) {
            ztId = jsonObject.getString("ztid");
            Zt zt = distManager.getZtReturnObj(ztId);
            if (zt == null) {
//                if (!StringUtils.contains(ztId, "test")) {
                    return new DuijieReturnMsg("账套编号参数有误，不存在编号为" + ztId + "的账套，请检查！");
//                }
//                zt = distManager.getZtReturnObj(StringUtils.replace(ztId, "test", ""));
            }
            distNo = zt.getDistNo();
            distName = zt.getDistName();
            ztName = zt.getZtName();
            ztJc = zt.getZtJc();
        } else {
            // 年报 不用账套编号的情况
            ztId = jsonObject.getString("distid");
            Dist dist = distManager.getDistByObj(ztId);
            if (dist == null) {
                return new DuijieReturnMsg("地区编号参数有误，不存在编号为" + ztId + "的地区，请检查！");
            }
            distNo = ztId;
            distName = dist.getDistName();
        }


        for (int i = 0; i < data.length(); i++) {
            JSONObject eachJsonObj = data.getJSONObject(i);

            eachJsonObj.put("qcNo", SystemConstant.QCNO);
            eachJsonObj.put("qcName", SystemConstant.QCNAME);
            eachJsonObj.put("years", SystemConstant.YEARS);
            eachJsonObj.put("distNo", distNo);
            eachJsonObj.put("distName", distName);
            eachJsonObj.put("ztid", ztId);
            eachJsonObj.put("ztName", ztName);
            eachJsonObj.put("ztJc", ztJc);
            eachJsonObj.put("distid", distNo);

            // _clientId 账套号+数据行号
            eachJsonObj.put("_clientSourceId", ztId + "-" + i);

            formattedData.put(eachJsonObj);
        }

        Duijie_ShiJieShouDeploy deploy = getDeployByMethodName(methodName);
        formattedJSONObj.put(deploy.getType().toUpperCase(), formattedData);
        RoleInfo roleInfo = dataManager.insertOrUpdate_toDB(user.getUserName(), formattedJSONObj, methodName, deploy);
        if (roleInfo == null) {
            return null;
        } else {
            // 还是有些信息要返回第三方 如字段为空 字段类型传得不对等等
            return new DuijieReturnMsg(roleInfo.getNoRoleInfo());
//            throw new RuntimeException("数据保存时出错:" + roleInfo.getNoRoleInfo());
        }
//        return null;
    }

    public List<String> checkDataValid(JSONObject jsonObject, String methodName) {
        // 数据校验方法 包括表内校验和表间校验
//        RoleInfo roleInfo;
//        StringBuilder errorMsg = new StringBuilder();
        Set<String> errorMsg = new LinkedHashSet<>();
        String ztId = jsonObject.has("ztid") ? jsonObject.getString("ztid") : jsonObject.getString("distid");
        JSONArray dataArray = jsonObject.getJSONArray("data");

//        if (years == 0) {
//            dataManager.initYears();
//        }

        Duijie_ShiJieShouDeploy deploy = getDeployByMethodName(methodName);
        String tableTypeName = deploy.getType();

        // 准备数据 封装tableDataMap  key=表名  value=数据列表
        Map<String, List<Map<String, String>>> tableDataMap = new HashMap<>();
        tableDataMap.put(tableTypeName, CommonUtil.changeMapValuesToString(dataArray));

        if (isQCHZTable(deploy.getTableName())) {
            // 清产核资的情况
            // 使用deploy的tablePanaName来关联filelist_newRules的tableName
            List<NewRules> newRules = duijie_qchz_getRuleManager.getCheckRulesByFileName(deploy.getTablePanaName());
            if (newRules.size() == 0) {
//                throw new RuntimeException("取不到规则！" + deploy.getTablePanaName());
                System.out.println("取不到规则！本表不做校验 " + deploy.getType());
            }
            // 记录没有数据的表
            Set<String> noDataTables = new HashSet<>();

            for (NewRules newRule : newRules) {
                switch (newRule.getType()) {
                    case 1:
                        if (newRule.getGsType() == 2) {
                            // 跨表 表间校验

                            // 获取需要用到的其他表 数据存到tableDataMap中
                            String[] otherTableTypeNames = newRule.getTableNameStr().split(",");
                            boolean hasTableNoData = getOtherTableDataReturnFlag(methodName, errorMsg, ztId, tableTypeName, tableDataMap, noDataTables, otherTableTypeNames, newRule.getDescription());
                            if (hasTableNoData) {
                                // hasTableNoData为true时 表明有至少一张需要依赖的表没有数据 要先传它的数据才能校验 已添加错误信息到errorMsg 跳过该条规则
                                continue;
                            }
                        }

                        errorMsg.addAll(QCHZExpressUtil.checkRuleType1(ztId, deploy.getDescription(), newRule.getRule(), newRule.getDescription(), tableDataMap));
                        break;
                    case 2:
                        //type2类型只支持表内关系，条件判断类型
                        errorMsg.addAll(QCHZExpressUtil.checkRuleType2(ztId, deploy.getDescription(), newRule.getRule(), newRule.getDescription(), tableDataMap.get(tableTypeName)));
                        break;
                    case 3:
                        //type3类型只支持表内关系（循环整表，每条纪录都要稽核）
                        errorMsg.addAll(QCHZExpressUtil.checkRuleType3(ztId, deploy.getDescription(), newRule.getRule(), newRule.getDescription(), tableDataMap.get(tableTypeName)));
                        break;
                    default:
                        throw new RuntimeException("校验类型type配置异常：" + newRule.getDescription());
//                    break;
                }
            }
        } else {
            // 年报的表
            // 获取年报的规则
            List<NewRules> newRules = duijie_qchz_getRuleManager.getNBCheckRulesByTableNameAndYears(deploy.getTablePanaName(), SystemConstant.YEARS);
            if (newRules.size() == 0) {
                throw new RuntimeException("取不到规则！" + deploy.getTablePanaName());
            }
            // 记录没有数据的表
            Set<String> noDataTables = new HashSet<>();

            for (NewRules newRule : newRules) {
                switch (newRule.getType()) {
                    case 0:
                        if (newRule.getGsType() == 2) {
                            // 跨表 表间校验
                            // TODO 获取需要用到的其他表
                            String[] otherTableTypeNames = NBExpressUtil.getOtherTableTypeNames(newRule.getLeftRule(), newRule.getRightRule());

                            // 获取需要用到的其他表 数据存到tableDataMap中
                            boolean hasTableNoData = getOtherTableDataReturnFlag(methodName, errorMsg, ztId, tableTypeName, tableDataMap, noDataTables, otherTableTypeNames, newRule.getDescription());
                            if (hasTableNoData) {
                                // hasTableNoData为true时 表明有至少一张需要依赖的表没有数据 要先传它的数据才能校验 已添加错误信息到errorMsg 跳过该条规则
                                continue;
                            }
                            List<String> msgList = NBExpressUtil.checkBetweenTables(ztId, SystemConstant.YEARS, deploy.getDescription(), newRule.getLeftRule(), newRule.getRightRule(), newRule.getExCond(), newRule.getSourceCondExpress(), newRule.getOpt(),
                                    newRule.getDescription(), tableDataMap);
                            errorMsg.addAll(msgList);

                        } else {
                            // 单表 横向校验
                            List<String> msgList = NBExpressUtil.checkHorizontal(ztId, SystemConstant.YEARS, deploy.getDescription(), newRule.getLeftRule(), newRule.getRightRule(), newRule.getExCond(), newRule.getSourceCondExpress(), newRule.getOpt(),
                                    newRule.getDescription(), tableDataMap.get(tableTypeName));
//                        errorMsg.addAll(QCHZExpressUtil.checkRuleType1(ztId, deploy.getDescription(), newRule.getRule(), newRule.getDescription(), tableDataMap));
                            errorMsg.addAll(msgList);
                        }
                        break;
                    case 1:
                        // TODO stype=1的情况暂不处理 需要时再实现
                        break;
                    case 2:
                        //stype=2的类型
//                        errorMsg.addAll(QCHZExpressUtil.checkRuleType3(ztId, deploy.getDescription(), newRule.getRule(), newRule.getDescription(), tableDataMap.get(tableTypeName)));
//                        NBExpressUtil
                        break;
                    default:
                        throw new RuntimeException("校验类型type配置异常：" + newRule.getDescription());
//                    break;
                }
            }

        }

        return new ArrayList<>(errorMsg);
    }

    // 数据校验 gstype=2 取跨表数据 数据存到参数tableDataMap中 返回一个布尔值
    private boolean getOtherTableDataReturnFlag(String methodName, Set<String> errorMsg, String ztId, String tableTypeName, Map<String, List<Map<String, String>>> tableDataMap,
                                                Set<String> noDataTables, String[] otherTableTypeNames, String ruleDescription) {
        // hasTableNoData: 表示该条规则下是否有依赖的表没有数据 需要它先传数据才能校验
        // noDataTables: 记录哪些表已经在数据库查过没有该账套下的数据
        boolean hasTableNoData = false;
//        String[] otherTableTypeNames = StringUtils.split(newRule.getTableNameStr(), ",");
        for (String otherTableType : otherTableTypeNames) {
            if (StringUtils.equalsIgnoreCase(otherTableType, tableTypeName)) {
                // 是本表 不用从数据库取
                continue;
            }
            if (!tableDataMap.containsKey(otherTableType)) { // 已经从数据库取过的表 不用再重复取
                // 从数据库查出该表的数据
                try {
                    // 跨表的情况 配置的跨表tableName与output表中的type字段挂钩
                    Duijie_ShiJieShouDeploy otherTableDeploy = getDeployByTypeName(otherTableType);
                    List<Map<String, String>> data = dataManager.getDataByTableNameAndZtId(otherTableType, ztId, otherTableDeploy);
                    tableDataMap.put(otherTableType, data);

                    // 检查另一张表是否存在数据 如果当前在传清产核资三张总表 则报错 其他不报错 比如年报跨表校验是两张表后传的那张才校验
                    if (data.size() == 0) { //  && isQCHZTable(tableTypeName) 20231128 由于跨表配置只配置在其中一边 所以没数据都要拦住
                        errorMsg.add("请先传该账套的" + otherTableDeploy.getDescription() + "（" + otherTableDeploy.getType() + "）数据！");
                        noDataTables.add(otherTableType);
                    }
                } catch (RuntimeException e) {
//                    log.error("数据校验取跨表数据时出错，ztId:" + ztId + ", 校验规则:" + newRule.getDescription() + ", 取表：" + otherTableType, e);
//                    errorMsg.add("校验数据时出错，请联系管理员：" + e.getMessage()); // 测试暂时放开 非测试要注释 并放开下面抛异常
                    noDataTables.add(otherTableType);
                    // 抛异常给上一层 由Duijie_QCHZ_ProcessAsyncThread内的异常捕获方法捕获
                    throw new RuntimeException("数据校验取跨表数据时出错，ztId:" + ztId + ", 校验规则:" + ruleDescription + ", 取表：" + otherTableType
                            + ", 错误：" + e.getMessage(), e);
                }
            }
            if (noDataTables.contains(otherTableType)) {
                hasTableNoData = true;
            }
        }
        return hasTableNoData;
    }

    public boolean isQCHZTable(String tableTypeName) {
        // 判断该表是否为清产核资的表
        // TODO ！！这里通过output的type以QC开头 判断是否为清产核资的三张总表！！
        return StringUtils.startsWithIgnoreCase(tableTypeName, "QC");
    }

    private Duijie_ShiJieShouDeploy getDeployByMethodName(String methodName) {
        List<Duijie_ShiJieShouDeploy> deploys = duijie_shiJieShouDeployManager.getDeploys(methodName);
        // 清产核资这边特殊 只有主表 为了方便识别校验表达式 尽量配合清产核资等已有配置 xmbm取filelist的fileName type取校验表达式中的表名 所以xmbm<>type
        // duijie_shiJieShouDeployManager.getDeploys方法中会另外将xmbm==deploy的那条数据放到第一位 没有就会新增一个null 所以我们要的deploy实际是第二个数据 deploys.get(1)
//        return deploys.get(1);

        // 修改了deployManager的实现类 现在只返回一个deploy 不会在第一位新增一个null
        return deploys.get(0);
    }

    private Duijie_ShiJieShouDeploy getDeployByTypeName(String typeName) {
        List<Duijie_ShiJieShouDeploy> mainDeploys = duijie_shiJieShouDeployManager.getMainDeploys();
        for (Duijie_ShiJieShouDeploy mainDeploy : mainDeploys) {
            if (StringUtils.equalsIgnoreCase(mainDeploy.getType(), typeName)) {
                return mainDeploy;
            }
        }
        return null;
    }


    public DuijieReturnMsg processDataAsync(Duijie_ShiJieShouUser user, JSONObject jsonObject, String methodName) {
        // 处理异步任务的方法 这个方法的返回值：正常的情况下 返回同步任务号；异常的情况下 返回错误信息
//        DuijieReturnMsg msgOuter = null;

        String ztId;
        if (jsonObject.has("ztid")) {
            ztId = jsonObject.getString("ztid");
        } else {
            ztId = jsonObject.getString("distid");
        }


        // 生成任务Id 用UUID
        // 改成用hutool的雪花算法生成
//        String taskId = user.getUserName() + UUID.randomUUID();
        String taskId = IdUtil.getSnowflakeNextIdStr();

        DuijieReturnMsg returnMsg = submitTaskService.submitTaskAndSave(new SubmitTaskCommand(taskId, methodName, SystemConstant.YEARS, ztId, user, jsonObject, this, taskManager));

//         在数据库的任务表中记录任务（合并到上面）
//        DuijieReturnMsg returnMsg = taskManager.insertTaskToDB(taskId, user.getUserName(), methodName, ztId);

        // 写入对接日志
        taskManager.writeToLogTable(jsonObject, user.getUserName(), methodName, taskId, ztId);

        return returnMsg;
    }


    /**
     * 在线程、消息队列监听类中执行的业务
     * @return
     */
    public DuijieReturnMsg processBusinessAsyncInThread(TaskAsync taskAsync) {
        DuijieReturnMsg msgInner = null;
        JSONObject jsonObject = new JSONObject(taskAsync.getJsonData());
        String methodName = taskAsync.getMethodName();
        String userName = taskAsync.getUserName();
        String taskId = taskAsync.getTaskId();

        try {

            // 先插入数据表
            taskManager.insertTaskToDB(taskAsync);

            List<String> errorMsg = checkDataValid(jsonObject, methodName);
            if (errorMsg.size() > 0) {
                msgInner = new DuijieReturnMsg("数据校验未通过，请检查！", errorMsg);
            }

            if (msgInner == null) {
                // 将数据存进数据库
                RoleInfo roleInfo = dataManager.insertOrUpdate_toDB(userName, jsonObject, methodName, getDeployByMethodName(methodName));
                msgInner = roleInfo == null ? null : new DuijieReturnMsg(roleInfo.getNoRoleInfo());
//                if (!DuijieReturnMsg.isSuccess(msgInner)) {
//                    // 这个分支是数据校验通过 已经在往数据库里存了 要出错也是只给我们自己内部人员看的错误 用抛异常的方式
//                    throw new RuntimeException("数据保存时出错：" + msgInner.getNoRoleInfo());
//                }
                // 存数据库也会出现 字段类型不对 字段不应为空 等等需要第三方处理的情况
            }

            // 先看异步任务表中是否有这个taskId，如果没有，可能是该线程执行太快，主线程createTask还没执行完导致表没这个任务号，休眠等待
//            int tryCount = 0;
//            while (tryCount <= 300 && taskManager.getTaskDataByTaskId(taskId, "1", "").size() == 0) {
//                System.out.println("正在等待createTask执行：" + taskId);
//                TimeUnit.MILLISECONDS.sleep(100);
//                tryCount++;
//            }
//            if (tryCount > 300) {
//                log.error(taskId + "等待30秒后仍未能在taskAsync中找到任务号 运行结果：" + DuijieReturnMsg.isSuccess(msgInner) + " " + msgInner.getNoRoleInfo());
//                return msgInner;
//            }

            // 任务执行完成 更新数据库中的任务状态
            if (DuijieReturnMsg.isSuccess(msgInner)) {
                taskManager.updateTaskStatus(taskId, userName, TaskManager.ASYNC_TASK_SUCCESS, "操作成功！", null);
                log.info(userName + "：" + taskId + "线程执行操作成功！");
            } else {
                if (msgInner.getNoRoleInfoList() != null) {
                    // 数据校验不通过
                    JSONArray errorInfoList = new JSONArray(msgInner.getNoRoleInfoList());
                    taskManager.updateTaskStatus(taskId, userName, TaskManager.ASYNC_TASK_CHECK_FAILED,
                            errorInfoList.toString(), null);
                    log.info(userName + "：" + taskId + "线程执行完成！数据校验出错：" + errorInfoList);
                } else {
                    // 其他异常情况
                    taskManager.updateTaskStatus(taskId, userName, TaskManager.ASYNC_TASK_ERROR,
                            msgInner.getNoRoleInfo(), null);
                    log.info(userName + "：" + taskId + "线程执行完成！返回异常：" + msgInner.getNoRoleInfo());
                }
            }

        } catch (Throwable error) {
            // 任务出错 更新数据库中的任务状态
            taskManager.updateTaskStatus(taskId, userName, TaskManager.ASYNC_TASK_ERROR,
                    "执行线程出错！请联系管理员！", error.getMessage());

            log.error(taskId + "执行线程出错：", error);
//            System.out.println(taskId + "出错！" + error.getMessage());
            return new DuijieReturnMsg("执行线程出错！请联系管理员！");
        }

        return msgInner;
    }

    public DuijieReturnMsg checkAsyncStatus() {

        StringBuilder status = new StringBuilder();
//
//        Collection<Callable<Integer>> tasks = new ArrayList<>();
//        for (CompletableFuture<Integer> future : testTaskList) {
//            tasks.add((Callable<Integer>) future);
//        }
//
//        try {
//            List<Future<Integer>> futures = executorService.invokeAll(tasks);
//            int i = 1;
//            for (Future<Integer> future : futures) {
//
//                status.append("进程").append(i).append("结果").append(future.isDone()).append(";\n");
//                i++;
//            }
//        } catch (InterruptedException e) {
//            throw new RuntimeException(e);
//        }


        return new DuijieReturnMsg(status.toString());
    }

}
